﻿using DJLang.Exceptions;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace DJLang
{
    public interface IDJValue : IEquatable<IDJValue>
    {
        IDJValue Evaluate(DJEnv env);
        IDJValue Clone();
    }

    public class DJNil : IDJValue
    {
        public IDJValue Clone() => this;

        public bool Equals(IDJValue other) => other is DJNil;

        public IDJValue Evaluate(DJEnv env) => this;
    }

    public abstract class DJSeq : IDJValue, IEnumerable<IDJValue>
    {
        protected List<IDJValue> values;

        public int Count => values.Count;

        public DJSeq()
        {
            values = new();
        }

        public DJSeq(IEnumerable<IDJValue> values)
        {
            this.values = values.ToList();
        }

        public IDJValue Peek()
        {
            return values.Count > 0 ? values[0] : new DJNil();
        }

        public IDJValue Pop(int index = 0)
        {
            var val = values[index];
            values.RemoveAt(index);
            return val;
        }

        public T PopAs<T>() where T : IDJValue => (T)Pop();

        public DJBasic<T> PopBasic<T>(int index = 0) where T : struct => Pop(index).AsBasic<T>();

        public DJRef<T> PopRef<T>(int index = 0) where T : class => Pop(index).AsRef<T>();

        public T PopValue<T>(int index = 0) where T : struct => Pop(index).GetValue<T>();

        public T PopManaged<T>(int index = 0) where T : class => Pop(index).GetManaged<T>();

        public void Push(IDJValue val)
        {
            values.Add(val);
        }

        public IDJValue EvaluateAll(DJEnv env)
        {
            for (int i = 0; i < values.Count; i++)
            {
                values[i] = values[i].Evaluate(env);
                if (values[i] is DJErr err) return err;
            }
            return this;
        }

        public bool Equals(IDJValue other)
        {
            if (other is DJSeq seq)
            {
                if (values.Count != seq.values.Count) return false;
                for (int i = 0; i < values.Count; i++)
                {
                    if (values[i] != seq.values[i]) return false;
                }
            }
            return false;
        }
        public abstract IDJValue Clone();
        public abstract IDJValue Evaluate(DJEnv env);

        public IEnumerator<IDJValue> GetEnumerator() => values.GetEnumerator();

        IEnumerator IEnumerable.GetEnumerator() => values.GetEnumerator();
    }

    public delegate IDJValue DJCallableDelegate(DJEnv env, DJSeq args);
    public interface IDJCallable : IDJValue
    {
        IDJValue Call(DJEnv env, DJSeq args);
    }

    public class DJBasic<T> : IDJValue where T : struct
    {
        public T val;
        public DJBasic()
        {
            val = default;
        }

        public DJBasic(T val)
        {
            this.val = val;
        }
        public virtual IDJValue Clone() => new DJBasic<T>(val);

        public bool Equals(IDJValue other) => val is IEquatable<T> && other is DJBasic<T> b && b.val.Equals(val);

        public IDJValue Evaluate(DJEnv env) => this;

        public override string ToString() => $"<Basic<{typeof(T)}>,{val}>";
    }

    public class DJRef<T> : IDJValue where T : class
    {
        public T val;
        public DJRef(T val)
        {
            this.val = val;
        }
        public virtual IDJValue Clone() => val is ICloneable cval ? new DJRef<T>((T)cval.Clone()) : this;

        public bool Equals(IDJValue other) => other is DJRef<T> r && r.val.Equals(val);

        public IDJValue Evaluate(DJEnv env) => this;

        public override string ToString() => $"<Ref<{typeof(T)}>,{val}>";
    }
    public static class DJRefExtensions
    {
        public static IDJValue Add(this DJRef<string> lhs, DJRef<string> rhs)
        {
            lhs.val += rhs.val;
            return lhs;
        }
    }

    public class DJErr : IDJValue
    {
        public string message;
        public DJErr(string msg)
        {
            message = msg;
        }

        public IDJValue Clone() => new DJErr(message);
        public bool Equals(IDJValue other) => false;

        public IDJValue Evaluate(DJEnv env) => this;

        public override string ToString() => $"Error:{message}";
    }

    public class DJSymbol : IDJValue
    {
        public string name;

        public DJSymbol(string name)
        {
            this.name = name;
        }

        public IDJValue Clone() => new DJSymbol(name);

        public bool Equals(IDJValue other) => other is DJSymbol s && s.name == name;

        public IDJValue Evaluate(DJEnv env) => env.Get(name);

        public override string ToString() => $"<Symbol,{name}>";
    }

    public class DJQuote : IDJValue
    {
        public string name;
        
        public DJQuote(string name)
        {
            this.name = name;
        }

        public IDJValue ToSymbol() => new DJSymbol(name);

        public IDJValue Clone() => new DJQuote(name);

        public bool Equals(IDJValue other) => other is DJQuote s && s.name == name;

        public virtual IDJValue Evaluate(DJEnv env) => this;
    }

    public class DJList : DJSeq
    {
        public DJList() { }
        public DJList(IEnumerable<IDJValue> values) : base(values) { }
        public DJList(params IDJValue[] values) : base(values) { }
        public DJExpr ToExpr() => new(values);
        public override IDJValue Clone() => new DJList(values.Select(x => x.Clone()).ToArray());
        public override IDJValue Evaluate(DJEnv env) => this;

        public override string ToString() => $"[{string.Join(',', values)}]";
    }

    public class DJExpr : DJSeq
    {
        public DJExpr() { }
        public DJExpr(IEnumerable<IDJValue> values) : base(values) { }
        public DJExpr(params IDJValue[] values) : base(values) { }
        public override IDJValue Clone() => new DJExpr(values.Select(x => x.Clone()).ToArray());
        public override IDJValue Evaluate(DJEnv env)
        {
            /*for (int i = 0; i < values.Count; i++)
            {
                values[i] = values[i].Evaluate(env);

                if (values[i] is DJErr err) return err;
            }*/

            if (Count == 0) return new DJErr("Trying to evaluate an empty expr");

            var first = Pop().Evaluate(env);

            return first is IDJCallable callable ? callable.Call(env, this) : new DJErr($"cant call '{first}'");
        }

        public override string ToString() => $"({string.Join(',', values)})";
    }

    

    public class DJBuiltin : IDJCallable
    {
        public DJCallableDelegate func;

        public DJBuiltin(DJCallableDelegate func)
        {
            this.func = func;
        }

        public IDJValue Call(DJEnv env, DJSeq args)
        {
            var evaluatedArgs = args.EvaluateAll(env);
            if (evaluatedArgs is DJSeq evaled)
                return func(env, evaled);
            return evaluatedArgs;
        }
        public IDJValue Clone() => new DJBuiltin(func);

        public bool Equals(IDJValue other) => other is DJBuiltin b && func.Equals(b.func);

        public IDJValue Evaluate(DJEnv env) => this;
    }

    public class DJBuiltinMacro : IDJCallable
    {
        public DJCallableDelegate func;

        public DJBuiltinMacro(DJCallableDelegate func)
        {
            this.func = func;
        }

        public IDJValue Call(DJEnv env, DJSeq args) => func(env, args);
        public IDJValue Clone() => new DJBuiltinMacro(func);

        public bool Equals(IDJValue other) => other is DJBuiltinMacro b && func.Equals(b.func);

        public IDJValue Evaluate(DJEnv env) => this;
    }

    public class DJClosure : IDJCallable
    {
        public DJList formals;
        public IDJValue body;

        public DJEnv localEnv;

        public DJClosure(IDJValue formals, IDJValue body) : this(formals, body, new DJEnv()) { }

        public DJClosure(IDJValue formals, IDJValue body, DJEnv localEnv)
        {
            this.formals = (DJList)formals;
            this.body = body;
            this.localEnv = localEnv;
        }

        public IDJValue Call(DJEnv env, DJSeq args)
        {
            args.EvaluateAll(env);

            int given = args.Count;
            int total = formals.Count;

            while (args.Count > 0)
            {
                if (formals.Count <= 0) return new DJErr($"args count({given}) less than formals({total}");

                var sym = (DJSymbol)formals.Pop();

                localEnv.Put(sym.name, args.Pop());
            }

            if (formals.Count <= 0)
            {
                localEnv.parent = env;
                return localEnv.ForceEval(body.Clone());
            }

            return Clone();
        }

        public IDJValue Clone() => new DJClosure(formals.Clone(), body.Clone());
        public bool Equals(IDJValue other) => false;

        public IDJValue Evaluate(DJEnv env) => this;
    }
}
